﻿using System;
using JetBrains.Annotations;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Interfaces.ViewModels;
using MugenMvvmToolkit.Models;
using MugenMvvmToolkit.Utils;
using MugenMvvmToolkit.ViewModels;

namespace MugenMvvmToolkit.Infrastructure
{
    /// <summary>
    ///     Represents the provider which creates a view models.
    /// </summary>
    public class ViewModelProvider : IViewModelProvider
    {
        #region Fields

        protected readonly IIocAdapter IocAdapter;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="ViewModelProvider" /> class.
        /// </summary>
        /// <param name="iocAdapter">The specified <see cref="IIocAdapter"/> value.</param>
        /// <param name="bindIocAdapter">If <c>true</c> it indicates that provider should bind IocAdapter to self, when creates the view-model.</param>
        public ViewModelProvider([NotNull] IIocAdapter iocAdapter, bool bindIocAdapter = false)
        {
            Should.NotBeNull(iocAdapter, "iocAdapter");
            IocAdapter = iocAdapter;
            BindIocAdapter = bindIocAdapter;
        }

        #endregion

        #region Properties

        /// <summary>
        ///     Gets or sets value, if <c>true</c> it indicates that provider should bind IocAdapter to self, when creates the view-model.
        /// </summary>
        public bool BindIocAdapter { get; set; }

        #endregion

        #region Implementation of IViewModelProvider

        /// <summary>
        ///     Creates an instance of the specified view model.
        /// </summary>
        /// <param name="getViewModel">The specified delegate to create view model.</param>
        /// <param name="policy">The specified <see cref="IActivationPolicy" />.</param>
        /// <param name="parentViewModel">The specified parent view model, if any.</param>
        /// <returns>
        ///     An instance of <see cref="IViewModel" />.
        /// </returns>
        public virtual IViewModel GetViewModel(GetViewModelDelegate<IViewModel> getViewModel, IActivationPolicy policy,
            IViewModel parentViewModel)
        {
            Should.NotBeNull(getViewModel, "getViewModel");
            if (policy == null)
                policy = GlobalSettings.DefaultActivationPolicy;
            IIocAdapter iocAdapter = CreateViewModelIocAdapter(policy, parentViewModel);
            IViewModel newViewModel = getViewModel(iocAdapter);
            if (!newViewModel.IsInitialized)
                InitializeViewModelInternal(newViewModel, policy, parentViewModel, iocAdapter);
            return newViewModel;
        }

        /// <summary>
        ///     Creates an instance of the specified view model.
        /// </summary>
        /// <param name="viewModelType">The type of view model.</param>
        /// <param name="policy">The specified <see cref="IActivationPolicy" />.</param>
        /// <param name="parentViewModel">The specified parent view model, if any.</param>
        /// <returns>
        ///     An instance of <see cref="IViewModel" />.
        /// </returns>
        public virtual IViewModel GetViewModel(Type viewModelType, IActivationPolicy policy, IViewModel parentViewModel)
        {
            Should.NotBeNull(viewModelType, "viewModelType");
            Should.BeViewModel(viewModelType, "viewModelType");
            if (policy == null)
                policy = GlobalSettings.DefaultActivationPolicy;
            return
                GetViewModel(
                    adapter =>
                        (IViewModel)adapter.Get(viewModelType, policy.ViewModelBindingName, policy.IocParameters),
                    policy, parentViewModel);
        }


        /// <summary>
        ///     Initializes specified <see cref="IViewModel" />, use this method if you have created an <see cref="IViewModel" />
        ///     without using the GetViewModel method.
        /// </summary>
        /// <param name="viewModel">
        ///     The specified <see cref="IViewModel" />.
        /// </param>
        /// <param name="policy">The specified <see cref="IActivationPolicy" />.</param>
        /// <param name="parentViewModel">The specified parent view model, if any.</param>
        public virtual void InitializeViewModel(IViewModel viewModel, IActivationPolicy policy, IViewModel parentViewModel)
        {
            Should.NotBeNull(viewModel, "viewModel");
            if (viewModel.IsInitialized)
                Thrower.ViewModelWasInitialized();
            if (policy == null)
                policy = GlobalSettings.DefaultActivationPolicy;
            InitializeViewModelInternal(viewModel, policy, parentViewModel, null);
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Initializes specified <see cref="IViewModel" />, use this method if you have created an <see cref="IViewModel" />
        ///     without using the GetViewModel method.
        /// </summary>
        protected void InitializeViewModelInternal(IViewModel viewModel, IActivationPolicy policy, IViewModel parentViewModel,
            IIocAdapter iocAdapter)
        {
            if (iocAdapter == null)
                iocAdapter = CreateViewModelIocAdapter(policy, parentViewModel);
            viewModel.InitializeViewModel(new InitializationContext(iocAdapter, policy, viewModel)
            {
                ParentViewModel = parentViewModel
            });
        }

        /// <summary>
        /// Creates an instance of IIocAdapter using activation policy and the parent view model if any.
        /// </summary>
        protected virtual IIocAdapter CreateViewModelIocAdapter([NotNull]IActivationPolicy activationPolicy, IViewModel parentViewModel)
        {
            return activationPolicy.CreateViewModelIocAdapter(IocAdapter, parentViewModel, BindIocAdapter);
        }

        #endregion
    }
}